package com.mald.plugin.util;

import com.mald.plugin.http.client.HttpClientResponse;
import com.mald.plugin.http.client.HttpCommonClient;
import com.mald.plugin.http.client.HttpConnectionPool;
import com.mald.plugin.http.exception.ResponseContentException;
import org.apache.http.Consts;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

/**
 * http请求通用方法
 *
 * @author
 * @since 2018/3/1
 */
public abstract class HttpClientUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtil.class);
    /**
     * 从连接池获取http连接的超时毫秒数
     */
    private static final int CONNECTION_REQUEST_TIMEOUT = 3000;
    /**
     * 建立三次握手的超时毫秒数
     */
    private static final int CONNECT_TIMEOUT = 3000;

    private static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8";

    private static final String CONTENT_TYPE_XML = "application/xml;charset=UTF-8";

    private static final Charset CHARSET = Consts.UTF_8;


    /**
     * 创建普通的http client
     *
     * @return
     */
    public static HttpClientUtil createDefault() {
        return new HttpCommonClient();
    }

    /**
     * 创建普通的http 连接池
     * 请单例使用
     *
     * @param maxTotal 连接池最大数量
     * @return
     */
    public static HttpClientUtil createHttpPool(int maxTotal) {
        return new HttpConnectionPool(maxTotal);
    }

    /**
     * 带url param 的post Xml
     *
     * @param url              请求地址
     * @param content          xml
     * @param readTimeOutMills 等待超时毫秒数
     * @param headers          请求头
     * @param urlParams        url params
     * @return
     * @throws IOException
     */
    public String httpPostXml(
            String url,
            String content,
            int readTimeOutMills,
            Map<String, String> headers,
            Map<String, String> urlParams) throws IOException {
        return httpPost(url, content, readTimeOutMills, headers, CONTENT_TYPE_XML, urlParams);
    }

    /**
     * 连接池：post请求xml
     *
     * @param url              请求地址
     * @param content          xml
     * @param readTimeOutMills 等待超时毫秒数
     * @param headers          请求头
     * @return
     */
    public String httpPostXml(
            String url,
            String content,
            int readTimeOutMills,
            Map<String, String> headers) throws IOException {
        return httpPostXml(url, content, readTimeOutMills, headers, null);
    }

    /**
     * 连接池：post请求JSON
     *
     * @param url              请求地址
     * @param content          json
     * @param readTimeOutMills 等待超时毫秒数
     * @param headers          请求头
     * @param urlParams        url params
     * @return
     */
    public String httpPostJson(
            String url,
            String content,
            int readTimeOutMills,
            Map<String, String> headers,
            Map<String, String> urlParams) throws IOException {
        return httpPost(url, content, readTimeOutMills, headers, CONTENT_TYPE_JSON, urlParams);
    }

    /**
     * 连接池：post请求JSON
     *
     * @param url              请求地址
     * @param content          json
     * @param readTimeOutMills 等待超时毫秒数
     * @param headers          请求头
     * @return
     */
    public String httpPostJson(
            String url,
            String content,
            int readTimeOutMills,
            Map<String, String> headers) throws IOException {
        return httpPostJson(url, content, readTimeOutMills, headers, null);
    }

    /**
     * http get请求
     *
     * @param url              请求地址
     * @param readTimeOutMills 等待超时毫秒数
     * @param headers          请求头
     * @return
     */
    public String httpGet(
            String url,
            int readTimeOutMills,
            Map<String, String> headers) throws IOException {
        RequestBuilder requestBuilder = RequestBuilder
                .get(url)
                .setCharset(CHARSET);
        return httpRequest(null, requestBuilder, readTimeOutMills, headers, null);
    }

    /**
     * post byte数组
     *
     * @param url              请求地址
     * @param requestByte      请求byte数组
     * @param readTimeOutMills 等待超时毫秒数
     * @param headers          请求头
     * @return
     */
    public byte[] httpPostByte(
            String url,
            byte[] requestByte,
            int readTimeOutMills,
            Map<String, String> headers) throws IOException {
        ByteArrayEntity entity = new ByteArrayEntity(requestByte);
        RequestBuilder builder = RequestBuilder
                .post(url)
                .setEntity(entity);
        return httpRequestByte(builder, readTimeOutMills, headers);
    }

    /**
     * get 获取byte数组
     *
     * @param url              请求地址
     * @param readTimeOutMills 等待超时毫秒数
     * @param headers          请求头
     * @return
     * @throws IOException http 状态码非200时，
     */
    public byte[] httpGetByte(
            String url,
            int readTimeOutMills,
            Map<String, String> headers) throws IOException {
        RequestBuilder builder = RequestBuilder
                .get(url);
        return httpRequestByte(builder, readTimeOutMills, headers);
    }

    /**
     * 字节方式请求
     *
     * @param requestBuilder   请求内容
     * @param readTimeOutMills 等待超时毫秒数
     * @param headers          请求头
     * @return
     * @throws IOException
     */
    private byte[] httpRequestByte(
            RequestBuilder requestBuilder,
            int readTimeOutMills,
            Map<String, String> headers) throws IOException {
        HttpClientResponse response = null;
        try {
            response = httpRequestStream(requestBuilder, readTimeOutMills, headers);
            int count = 1024;
            byte[] buf = new byte[1024];
            ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
            while ((count = response.getResponse().getEntity().getContent().read(buf, 0, count)) > 0) {
                byteArray.write(buf, 0, count);
            }
            int responseStatus = response.getResponse().getStatusLine().getStatusCode();
            if (responseStatus != HttpStatus.SC_OK) {
                String message = new String(byteArray.toByteArray());
                LOGGER.error("request failed! http status:{},response message:{}", responseStatus, message);
                throw new ResponseContentException(message);
            }
            return byteArray.toByteArray();
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    /**
     * http post请求
     *
     * @param postUrl          请求url
     * @param postContent      请求内容
     * @param readTimeOutMills 超时毫秒数
     * @param headers          请求头
     * @param contentType      内容类型
     * @param params            url params
     * @return
     */
    private String httpPost(
            String postUrl,
            String postContent,
            int readTimeOutMills,
            Map<String, String> headers,
            String contentType,
            Map<String, String> params) throws IOException {
        StringEntity entity = new StringEntity(postContent, CHARSET);
        entity.setContentType(contentType);
        RequestBuilder requestBuilder = RequestBuilder
                .post(postUrl)
                .setCharset(CHARSET)
                .setEntity(entity);
        if (headers == null) {
            headers = new HashMap<>();
        }
        headers.put("Accept", contentType);
        return httpRequest(postContent, requestBuilder, readTimeOutMills, headers, params);
    }


    public abstract CloseableHttpClient getHttpClient();

    public abstract void releaseConnection(CloseableHttpClient httpClient) throws IOException;

    /**
     * post
     *
     * @param content            请求内容
     * @param timeoutMillSeconds 超时毫秒数
     * @param headers            header
     * @param params             url param
     * @return
     */
    private String httpRequest(
            String content,
            RequestBuilder requestBuilder,
            int timeoutMillSeconds,
            Map<String, String> headers, Map<String, String> params) throws IOException {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
                .setConnectTimeout(CONNECT_TIMEOUT)
                .setSocketTimeout(timeoutMillSeconds)//等待响应超时毫秒数
                .build();
        CloseableHttpClient httpClient = this.getHttpClient();
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(requestBuilder::addHeader);
        }
        if (params != null && !params.isEmpty()) {
            params.forEach(requestBuilder::addParameter);
        }
        HttpUriRequest httpUriRequest = requestBuilder.setConfig(requestConfig).build();
        CloseableHttpResponse response = null;
        String responseMsg;
        try {
            LOGGER.info("*******************begin********************");
            LOGGER.info("requestUrl:{}", httpUriRequest.getURI());
            LOGGER.info("requestMethod:{}", httpUriRequest.getMethod());
            LOGGER.info("requestBody:{}", content);
            response = httpClient.execute(httpUriRequest);
            LOGGER.info("httpCode:{}", response.getStatusLine().getStatusCode());
            responseMsg = EntityUtils.toString(response.getEntity(), CHARSET);
            LOGGER.info("response:{}", responseMsg);
            LOGGER.info("*******************end********************");
        } catch (IOException e) {
            LOGGER.error("request failed!");
            LOGGER.error("requestUrl:{}", httpUriRequest.getURI());
            LOGGER.error("requestMethod:{}", httpUriRequest.getMethod());
            LOGGER.error("requestBody:{}", content);
            throw e;
        } finally {
            this.releaseConnection(httpClient);
            if (response != null) {
                response.close();
            }
        }
        return responseMsg;
    }

    /**
     * stream 方式请求
     *
     * @param requestBuilder     请求内容
     * @param timeoutMillSeconds 超时毫秒数
     * @param headers            header
     * @return
     */
    public HttpClientResponse httpRequestStream(
            RequestBuilder requestBuilder,
            int timeoutMillSeconds,
            Map<String, String> headers) throws IOException {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
                .setConnectTimeout(CONNECT_TIMEOUT)
                .setSocketTimeout(timeoutMillSeconds)//等待响应超时毫秒数
                .build();
        CloseableHttpClient httpClient = this.getHttpClient();
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(requestBuilder::addHeader);
        }
        HttpUriRequest httpUriRequest = requestBuilder.setConfig(requestConfig).build();
        CloseableHttpResponse response = null;
        try {
            LOGGER.info("*******************begin********************");
            LOGGER.info("requestUrl:{}", httpUriRequest.getURI());
            LOGGER.info("requestMethod:{}", httpUriRequest.getMethod());
            response = httpClient.execute(httpUriRequest);
            LOGGER.info("httpCode:{}", response.getStatusLine().getStatusCode());
            LOGGER.info("*******************end********************");
        } catch (IOException e) {
            LOGGER.error("request failed!");
            LOGGER.error("requestUrl:{}", httpUriRequest.getURI());
            LOGGER.error("requestMethod:{}", httpUriRequest.getMethod());
            throw e;
        }
        return new HttpClientResponse(this, httpClient, response);
    }


    /**
     * get请求，获取返回流，切记关闭response
     * <p>
     * 获取返回流：
     * httpClientResponse.getResponse().getEntity().getContent();
     * <p>
     * 获取HTTP状态码：
     * httpClientResponse.getResponse().getStatusLine().getStatusCode();
     *
     * @param url              请求地址
     * @param readTimeOutMills 超时毫秒数
     * @param headers          请求头
     * @return
     * @throws IOException
     */
    public HttpClientResponse httpGetReceiveContent(
            String url,
            int readTimeOutMills,
            Map<String, String> headers) throws IOException {
        RequestBuilder requestBuilder = RequestBuilder
                .get(url)
                .setCharset(CHARSET);
        HttpClientResponse response = this.httpRequestStream(requestBuilder, readTimeOutMills, headers);
        LOGGER.info("response content length:{}", response.getResponse().getEntity().getContentLength());
        return response;
    }


}
